package taskit.logic;

import java.util.*;

import taskit.customexceptions.UnrecognizedDateFormatException;
import taskit.customexceptions.WrongDateException;
import taskit.datastructures.Date;
import taskit.test.Log;
//@author A0105301E
public class DateFormatter {


	private static final String AM = "am";

	private static final String PM = "pm";

	// An array of all the months of the year
	public static final String[] MONTHS = { "Jan", "Feb", "Mar", "Apr", "May",
		"Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };

	// An array of all the weekdays
	public static final String[] WEEKDAYS = { "Sunday", "Monday", "Tuesday",
		"Wednesday", "Thursday", "Friday", "Saturday" };

	// An array of the weekdays written in short
	public static final String[] WEEKDAYS_SHORT = { "Sun", "Mon", "Tue", "Wed",
		"Thur", "Fri", "Sat" };



	/*
	 * An array of all the months with values assigned to each January has been
	 * assigned value 0 whereas December is 11
	 */
	public static final String[] MONTH_VALUES = { "jan=1", "feb=2", "mar=3",
		"apr=4", "may=5", "jun=6", "jul=7", "aug=8", "sep=9", "oct=10",
		"nov=11", "dec=12", "january=1", "febuary=2", "march=3", "april=4",
		"may=5", "june=6", "july=7", "august=8", "september=9",
		"october=10", "november=11", "december=12" };

	// An array of all the numbers until 31 spelt with corresponding values
	// assigned to each
	public static final String[] DATE_IN_NUM = { "one=1", "two=2", "three=3",
		"four=4", "five=5", "six=6", "seven=7", "eight=8", "nine=9",
		"ten=10", "eleven=11", "twelve=12", "thirteen=13", "fourteen=14",
		"fifteen=15", "sixteen=16", "seventeen=17", "eighteen=18",
		"nineteen=19", "twenty=20", "twentyone=21", "twentytwo=22",
		"twentythree=23", "twentyfour=24", "twentyfive=25", "twentysix=26",
		"twentyseven=27", "twentyeight=28", "twentynine=29", "thirty=30",
	"thirtyone=31" };

	// An array of all the numbers until 31 spelt in different ways indicating
	// position, eg. 4th or fourth
	public static final String[] DATE_IN_POS = { "first=1", "second=2",
		"third=3", "fourth=4", "fifth=5", "sixth=6", "seventh=7",
		"eighth=8", "ninth=9", "tenth=10", "eleventh=11", "twelfth=12",
		"thirteenth=13", "fourteenth=14", "fifteenth=15", "sixteenth=16",
		"seventeenth=17", "eighteenth=18", "nineteenth=19", "twentieth=20",
		"twentyfirst=21", "twentysecond=22", "twentythird=23",
		"twentyfourth=24", "twentyfifth=25", "twentysixth=26",
		"twentyseventh=27", "twentyeighth=28", "twentyninth=29",
		"thirtieth=30", "thirtyfirst=31", "1st=1", "2nd=2", "3rd=3",
		"4th=4", "5th=5", "6th=6", "7th=7", "8th=8", "9th=9", "10th=10",
		"11th=11", "12th=12", "13th=13", "14th=14", "15th=15", "16th=16",
		"17th=17", "18th=18", "19th=19", "20th=20", "21st=21", "22nd=22",
		"23rd=23", "24th=24", "25th=25", "26th=26", "27th=27", "28th=28",
		"29th=29", "30th=30", "31st=31" };

	/*
	 * all the weekday values (short and long) with values assigned to each week
	 * is assumed to start from sunday which has value 1 assigned to it
	 */
	public static final String[] WEEKDAY_VALUES = { "monday=2", "tuesday=3",
		"wednesday=4", "thursday=5", "friday=6", "mon=2", "tue=3", "wed=4",
		"thu=5", "fri=6", "sat=7", "sun=1", "saturday=7", "sunday=1" };

	// different time formats : am/pm(12 hour clock) or hrs(24 hour clock)
	public static final String[] LIST_TIME_FORMATS = { "am=0", "pm=12", "hrs=0" };

	/*
	 * various prefixes with corresponding values next always refers to the one
	 * in the next week this always refers to that <weekday> in the present week
	 * last always refers to the <weekday> in the last week
	 */
	public static final String[] PREFIXES = { "next=1", "nexttonext=2",
		"last=-1", "lasttolast=-2", "this=0", "coming=0" };

	public static final String[] MOVE_BY_DAY = { "today=0", "tomorrow=1",
		"yesterday=-1", "dayAfterTomorrow=2", "dayBeforeYesterday=-2" };

	// coming always refers to the first <weekday> encountered
	public static final String COMING = "coming";

	public static final String WEEK = "week";

	public static final String MONTH = "month";

	public static final String YEAR = "year";

	public static final int TWELVE = 12;

	public static final String HOURS = "hrs";

	public static final String TO = " to ";

	public static final String THIS = "this";

	public static final String FWD_SLASH = "/";

	public static final String HYPHEN = "-";

	public static final String BACK_SLASH = "\\";

	public static final String SEMICOLON = ":";

	public static final String EQUAL_SIGN = "=";

	static boolean foundMatch = false;
	static boolean foundDayMatch = false;
	static boolean foundMonthMatch = false;
	static boolean foundYearMatch = false;
	static boolean foundTime = true;

	public static void main(String[] args) throws Exception {

		// *****************************************************************************************************************************************
		// some examples to illustrate various possible formats

		System.out.println(format(parse("may/02/2014 8:00am")));
		System.out.println(format(parse("02-05")));
		System.out.println(format(parse("14 march 2014 3hrs")));
		System.out.println(format(parse("today 2hrs")));
		System.out.println(format(parse("next friday 6am"))); 
		System.out.println(format(parse("next week tuesday 0hrs")));
		System.out.println(format(parse("this wednesday 2:00pm")));
		System.out.println(format(parse("april 14 20:00hrs")));
		List<Date> list = parseSchedule("today to tomorrow 21:00hrs");
		System.out.println(format(list.get(0), list.get(1)));
		list = parseSchedule("tue 2pm to 5pm");
		System.out.println(format(list.get(0), list.get(1))); 

		// *****************************************************************************************************************************************

	}

	// @author A0098733R
	/**
	 * function to format a "date" object into a string prefixes the date with
	 * suitable titles such as today, tomorrow, next week formats the time into
	 * am/pm
	 * 
	 * @param date
	 */
	public static String format(Date date) {
		boolean isRelated = false;
		String day, week = "", fullDate = "", min = "";
		GregorianCalendar currentTime = new GregorianCalendar();
		GregorianCalendar givenTime = new GregorianCalendar(date.getYear(),
				date.getMonth(), date.getDay(), date.getHour(), date.getMins());

		if (equals(currentTime, givenTime)) {
			day = "Today";
			isRelated = true;
		} else {
			currentTime.add(Calendar.DAY_OF_YEAR, -1);
			if (equals(currentTime, givenTime)) {
				day = "Yesterday";
				isRelated = true;
			} else {
				currentTime.add(Calendar.DAY_OF_YEAR, 2);
				if (equals(currentTime, givenTime)) {
					isRelated = true;
					day = "Tomorrow";
				} else {
					day = WEEKDAYS[givenTime.get(Calendar.DAY_OF_WEEK) - 1];
				}
			}
		}
		currentTime = new GregorianCalendar();
		if (!isRelated) {
			if (equalsWeek(currentTime, givenTime)) {
				week = " this week";
			} else {
				currentTime.add(Calendar.WEEK_OF_YEAR, -1);
				if (equalsWeek(currentTime, givenTime)) {
					week = " last week";
				} else {
					currentTime.add(Calendar.WEEK_OF_YEAR, 2);
					if (equalsWeek(currentTime, givenTime)) {
						week = " next week";
					}
				}
			}
			fullDate = ", " + date.getDay() + " " + MONTHS[date.getMonth()] + " "
					+ date.getYear();
		}

		int hours = date.getHour();
		int mins = date.getMins();
		if (mins >= 10) {
			min = "." + mins;
		} else if (mins > 0) {
			min = ".0" + mins;
		}
		return day + week + fullDate + " " + getFormattedTime(min, hours);
	}

	/**
	 * overloaded function format takes in two parameters and returns a string
	 * 
	 * @param start
	 * 
	 * @param end
	 */

	public static String format(Date start, Date end) {
		String first, second;

		first = format(start);
		if (start.extractDate().equals(end.extractDate())) {
			int hours = end.getHour();
			int mins = end.getMins();
			String min = "";
			if (mins != 0) {
				min = String.format(".%2d", mins);
			}
			second = getFormattedTime(min, hours);
		} else {
			second = format(end);
		}
		return first + TO + second;
	}

	/**
	 * this function returns the time along with the appropriate suffix in
	 * string form
	 * 
	 * @param min
	 * 
	 * @param hours
	 */

	private static String getFormattedTime(String min, int hours) {
		if (hours > 12) {
			return (hours - 12) + min + PM;
		} else if (hours == 12) {
			return (hours) + min + PM;
		} else if (hours > 0) {
			return hours + min + AM;
		} else if (hours == 0) {
			return 12 + min + AM;
		} else {
			return 12 + min + PM;
		}
	}


	//@author A0105301E

	/**
	 *  this function returns a date with the time initialized according to the string passed
	 *  
	 *  @param s
	 *  
	 *  @exception UnrecognizedDateFormatException : when the date format is unrecognized
	 * 
	 */

	public static Date initializeTime(String s) throws Exception {
		int day = 1, month = 1, year = 1, hrs = 0, mins = 0, i = 0;
		foundTime = false;

		for (String st : LIST_TIME_FORMATS) {

			int index = st.indexOf(EQUAL_SIGN);
			if (s.toLowerCase().contains(st.substring(0, index))) {
				if (s.contains(SEMICOLON)) {
					String[] timeParams = s.split(SEMICOLON);
					if(timeParams[0].length()==0 || timeParams[1].length()==0) {
						throw new UnrecognizedDateFormatException("Entered Date Format is unrecognized !"); 
					}
					for (i = 0; i < timeParams[0].length(); i++) {
						if (!Character.isDigit(timeParams[0].charAt(i))) {
							if(!(i==0 && (timeParams[0].charAt(i)==HYPHEN.charAt(0)))){
								break;
							}
						}
					}
					if(i!=timeParams[0].length() || i==0){
						throw new UnrecognizedDateFormatException("Entered Date Format is unrecognized !"); 
					}
					hrs = Integer.parseInt(timeParams[0]);
					if(hrs<0){
						throw new WrongDateException("Time cannot be negative");
					}
					if((hrs>12) && (!st.contains(HOURS))){
						throw new UnrecognizedDateFormatException("Entered Date Format is unrecognized !");
					}
					if ((hrs == TWELVE) && (!st.contains(HOURS))) {
						hrs = hrs - 12;
					}
					hrs = hrs + Integer.parseInt(st.substring(index + 1));
					int posTimeFormat = timeParams[1].indexOf(st.substring(0, index));
					for (i = 0; i < posTimeFormat; i++) {
						if (!Character.isDigit(timeParams[1].charAt(i))) {
							break;
						}
					}
					if(i!=posTimeFormat || posTimeFormat==0){
						throw new UnrecognizedDateFormatException("Entered Date Format is unrecognized !"); 
					}
					mins = Integer.parseInt(timeParams[1].substring(0, i));
					foundTime = true;
				} else {
					int posTimeFormat = s.indexOf(st.substring(0, index));
					for (i = 0; i < posTimeFormat; i++) {
						if (!Character.isDigit(s.charAt(i))) {
							if(!(i==0 && (s.charAt(i)==HYPHEN.charAt(0)))){
								break;
							}
						}
					}
					if(i!=posTimeFormat || posTimeFormat==0){ 
						throw new UnrecognizedDateFormatException("Entered Date Format is unrecognized !"); 
					}
					hrs = Integer.parseInt(s.substring(0, i));
					if(hrs<0){
						throw new WrongDateException("Time cannot be negative");
					}
					if((hrs>12) && (!st.contains(HOURS))){
						throw new UnrecognizedDateFormatException("Entered Date Format is unrecognized !");
					}
					if ((hrs == TWELVE) && (!st.contains(HOURS))) {
						hrs = hrs - 12;
					}
					hrs = hrs + Integer.parseInt(st.substring(index + 1));
					mins = 0;
					foundTime = true;
				}
			}
		}
		Log.add("taskit.logic.DateFormatter.java : create a new date object");
		return Date.createDate(year, month, day, hrs, mins);
	}


	/** 
	 *  Function which extracts the year from a given string
	 * If the extraction is successful it updates the foundYearMatch to true
	 * 
	 * @param str
	 * 
	 * @return   the extracted year if given string contains year
	 *     		 0 if parse unsuccessul
	 *     
	 * @throws	 WrongDateException if year parsed is negative
	 * 
	 */

	public static int initializeYear(String str) throws Exception {
		int year = 0;
		int i = 0;
		for (i = 0; i < str.length(); i++) {
			if (!Character.isDigit(str.charAt(i))) {
				if(!(i==0 && (str.charAt(i)==HYPHEN.charAt(0)))){
					break;
				}
			}
		}
		if (i == str.length()) {
			year = Integer.parseInt(str);
			if(year<0) {
				throw new WrongDateException("Year cannot be negative!");	
			}
			foundYearMatch = true;
		}
		return year;
	}

	/** 
	 * This function returns the current year
	 * 
	 * @return the current year
	 */

	public static int setToCurrentYear() {

		int year;
		GregorianCalendar now = new GregorianCalendar();
		year = now.get(GregorianCalendar.YEAR);
		foundYearMatch = true;
		return year;

	}

	/**  
	 * This function initializes date to today's date 
	 * and the time to 11:59pm
	 * 
	 * @return a date object initialized to today's date
	 * 
	 * 
	 * @throws Exception WrongDateException
	 */

	public static Date initializeDefaultDate() throws Exception {

		int year, month, day, hrs, mins;
		hrs = 23;
		mins = 59;
		GregorianCalendar now = new GregorianCalendar();
		day = now.get(GregorianCalendar.DATE);
		month = now.get(GregorianCalendar.MONTH);
		year = now.get(GregorianCalendar.YEAR);
		Log.add("taskit.logic.DateFormatter.java : create a new date object");
		return Date.createDate(year, month, day, hrs, mins);

	}


	/**
	 * a function which parses the string to extract day 
	 * If parse is successful, then foundDayMatch is set to true
	 * 
	 * @param str
	 * 
	 * @return  day, if parse is successful
	 *     0, if parse is unsuccessful 
	 */

	public static Integer findDayMatch(String str) {

		int day = 0;
		if (Character.isDigit(str.charAt(0))) {
			int a = 0;
			for (a = 0; a < str.length(); a++) {
				if (Character.isLetter(str.charAt(a))) {
					break;
				}
			}
			if (a == str.length()) {
				day = Integer.parseInt(str);
				foundDayMatch = true;
				return day;
			}
		}

		if (foundDayMatch == false) {
			for (String st : DATE_IN_NUM) {
				int index = st.indexOf(EQUAL_SIGN);
				if (st.substring(0, index).equals(str.toLowerCase())) {
					day = Integer.parseInt(st.substring(index + 1));
					foundDayMatch = true;
					return day;
				}
			}

			for (String st : DATE_IN_POS) {
				int index = st.indexOf(EQUAL_SIGN);
				if (st.substring(0, index).equals(str.toLowerCase())) {
					day = Integer.parseInt(st.substring(index + 1));
					foundDayMatch = true;
					return day;
				}
			}
		}
		//when the control reaches here, foundDayMatch must be false
		assert foundDayMatch==false;
		return day;
	}


	/** 
	 * a function which parses the string to extract month 
	 * If parse is successful, then foundMonthMatch is set to true
	 * 
	 * @param str
	 * 
	 * @return  month, if parse is successful
	 *     0, if parse is unsuccessful 
	 */
	public static int findMonthMatch(String str) throws Exception{

		int month = 0;
		int i;
		for( i =0; i<str.length(); i++) {
			if (!Character.isDigit(str.charAt(i))) {
				if(!(i==0 && str.charAt(i)==HYPHEN.charAt(0))) {
					break;
				}
			}
		}
		if (i==str.length()) {
			month = Integer.parseInt(str);
			if(month<0) {
				throw new WrongDateException("Month cannot be negative");
			}
			foundMonthMatch = true;
			return month;
		} else {
			int temp_month = findMonthInWords(str);
			if(foundMonthMatch==true) {
				month = temp_month;
				return month;
			}
		}
		//when the control reaches here, foundMonthMatch must be false
		assert foundMonthMatch==false;
		return month;
	}

	/**
	 * Function to find month in words eg. January, Feb 
	 * Updates foundMonthMatch to true if match is found
	 * 
	 * @param string
	 * 
	 * @return the extracted month, if parse is successful
	 *    0, if parse was unsuccessful
	 */

	public static int findMonthInWords(String string) {
		int month = 0;
		for (String str : MONTH_VALUES) {
			int index = str.indexOf(EQUAL_SIGN);
			if (str.substring(0, index).equals(string.toLowerCase())) {
				month = Integer.parseInt(str.substring(index + 1));
				foundMonthMatch = true;
				return month;
			}
		}
		//when the control reaches here the foundMonthMatch must be false
		assert foundMonthMatch==false;
		return month;

	}


	/**
	 * Function extracts the weekday from a given string 
	 * if extraction is successful, it set foundDayMatch, foundMonthMatch, and foundYearMatch to true
	 * 
	 * @param prefix
	 * @param add
	 * @param token
	 * 
	 * @return  a date initialized to extracted weekday
	 * 
	 * @throws Exception 
	 */

	public static Date findWeekdayMatch(String prefix, int add, String token)
			throws Exception {

		int year = 1, month = 1, day = 1, hrs = 0, mins = 0;

		for (String str : WEEKDAY_VALUES) {
			if (str.startsWith(token.toLowerCase())) {
				//if a match for a weekday is found
				int index1 = str.indexOf(EQUAL_SIGN);
				int givenWeekday = Integer.parseInt(str.substring(index1 + 1));
				GregorianCalendar now = new GregorianCalendar();
				int diffDays = givenWeekday
						- now.get(GregorianCalendar.DAY_OF_WEEK);

				if (add > 0) {
					now.add(GregorianCalendar.WEEK_OF_YEAR, add);

				} else if (add < 0) {
					now.add(GregorianCalendar.WEEK_OF_YEAR, add);

				} else {
					if (prefix.startsWith(COMING)) {
						if (diffDays <= 0) {
							now.add(GregorianCalendar.WEEK_OF_YEAR, 1);
						}
					}
				}
				now.add(GregorianCalendar.DAY_OF_WEEK, diffDays);

				day = now.get(Calendar.DATE);
				month = now.get(Calendar.MONTH) + 1;
				year = now.get(Calendar.YEAR);

				foundDayMatch = true;
				foundMonthMatch = true;
				foundYearMatch = true;
				foundMatch = true;

				break;
				// found weekday
			}
		}
		//when the control reaches here the foundMatch should be false
		assert foundMatch==false;
		Log.add("taskit.logic.DateFormatter.java : create a new date object");
		return Date.createDate(year, month, day, hrs, mins);
	}


	/** 
	 * This function tries to find a prefix match such as next, last
	 * it then finds a match for weekday, eg: next week Friday or this Tuesday
	 * if parse is successful, it sets foundDayMatch to true
	 * 
	 * @param tokens
	 * @param start
	 *
	 * @return a date object whose date is initialized to the appropriate date
	 * 
	 * @throws Exception	WrongDateException when date is invalid
	 * 						UnrecognizedDateFormat exception when date format cannot be recognized
	 */

	public static Date findPrefixMatch(String[] tokens, int start)
			throws Exception {

		int day = 1, month = 1, year = 1, hrs = 0, mins = 0;
		for (String s : PREFIXES) {
			int index = s.indexOf(EQUAL_SIGN);
			int add = Integer.parseInt(s.substring(index + 1));
			if (s.substring(0, index).equals(tokens[start].toLowerCase())) {
				if (tokens[start + 1].toLowerCase().equals(WEEK)) {
					Date temp_date = findWeekdayMatch(s, add, tokens[start + 2]);
					if (foundMatch == true) {
						if((tokens.length>start+4) || (tokens.length==start+4 && !isTime(tokens[start+3]))){
							throw new UnrecognizedDateFormatException("Entered Date Format is unrecognized !");
						}
						day = temp_date.getDay();
						month = temp_date.getMonth();
						year = temp_date.getYear();
						Log.add("taskit.logic.DateFormatter.java : create a new date object");
						return Date.createDate(year, month, day, hrs, mins);
					}
				}
				// end of if week
				else {
					Date temp_date = findWeekdayMatch(s, add, tokens[start + 1]);
					if (foundMatch == true) {
						if((tokens.length>start+3) || (tokens.length==start+3 && !isTime(tokens[start+2]))){
							throw new UnrecognizedDateFormatException("Entered Date Format is unrecognized !");
						}
						day = temp_date.getDay();
						month = temp_date.getMonth();
						year = temp_date.getYear();
						Log.add("taskit.logic.DateFormatter.java : create a new date object");
						return Date.createDate(year, month, day, hrs, mins);
					}
				}
			}
		}
		//when the control reaches here foundMatch should be false
		assert foundMatch==false;
		return Date.createDate(year, month, day, hrs, mins);
	}


	/**
	 * This function tries to parse strings for a match such as today, tomorrow, yesterday
	 * If parse is successful it sets the foundMatch to true
	 * 
	 * @param str
	 * 
	 * @return a date object initialized to parsed date
	 * 
	 * @throws Exception	WrongDateException when date is invalid
	 * 						UnrecognizedDateFormat exception when date format cannot be recognized
	 */

	public static Date findMovingByDay(String str) throws Exception {

		int day = 1, month = 1, year = 1, hrs = 0, mins = 0;
		for (String st : MOVE_BY_DAY) {
			int ind = st.indexOf(EQUAL_SIGN);
			if (st.substring(0, ind).equals(str.toLowerCase())) {
				GregorianCalendar now = new GregorianCalendar();
				now.add(GregorianCalendar.DAY_OF_YEAR,
						Integer.parseInt(st.substring(ind + 1)));
				day = now.get(Calendar.DATE);
				month = now.get(Calendar.MONTH) + 1;
				year = now.get(Calendar.YEAR);
				foundDayMatch = true;
				foundMonthMatch = true;
				foundYearMatch = true;
				foundMatch = true;
				return Date.createDate(year, month, day, hrs, mins);
			}
		}
		//when the control reaches here the foundMatch should be false
		assert foundMatch==false;
		return Date.createDate(year, month, day, hrs, mins);
	}
	
	/** 
	 * Function to check if given string represents time or not
	 * 
	 * @param str
	 * 
	 * @return <code>true</code> if string represents time
	 *    <code>false</code> if string doesn't have time
	 */
	public static boolean isTime(String str) {
		boolean time = true;
		for (String st : LIST_TIME_FORMATS) {
			int index = st.indexOf(EQUAL_SIGN);
			if (str.toLowerCase().endsWith(st.substring(0, index))) {
				return time;
			}

		}
		time = false;
		return false;
	}

	/**
	 * 
	 * @function to check if in the given string time parameter is missing
	 * 
	 * @param str
	 * 
	 * @return  <code>true</code> if time is missing
	 *     <code>false</code> if time parameter is missing
	 */

	public static boolean isTimeMissing(String str) {

		boolean missing = true;
		for (String st : LIST_TIME_FORMATS) {
			int index = st.indexOf(EQUAL_SIGN);
			if (str.toLowerCase().contains(st.substring(0, index))) {
				missing = false;
				return missing;
			}
		}
		return missing;
	}

	//@author A0098733R
	/**
	 * This function checks whether two GregorianCalendar objects are on the same day
	 * 
	 * @param time1
	 * @param time2
	 * @return  <code>true</code> if two GregorianCalendar objects are on the same day,
	 *     <code>false</code> otherwise
	 */
	public static boolean equals(GregorianCalendar time1,
			GregorianCalendar time2) {
		return (time1.get(Calendar.YEAR) == time2.get(Calendar.YEAR))
				&& (time1.get(Calendar.MONTH) == time2.get(Calendar.MONTH))
				&& (time1.get(Calendar.DAY_OF_MONTH) == time2
				.get(Calendar.DAY_OF_MONTH));
	}

	/**
	 * This function checks whether two GregorianCalendar objects are on the same week
	 * 
	 * @param time1
	 * @param time2
	 * @return  <code>true</code> if two GregorianCalendar objects are on the same week,
	 *     <code>false</code> otherwise
	 */
	public static boolean equalsWeek(GregorianCalendar time1,
			GregorianCalendar time2) {
		return (time1.get(Calendar.YEAR) == time2.get(Calendar.YEAR))
				&& (time1.get(Calendar.WEEK_OF_YEAR) == time2
				.get(Calendar.WEEK_OF_YEAR));
	}

	//@author A0105301E

	/**
	 * This function extracts date and time from string passed
	 * 
	 * @param string
	 * 
	 * @return Date object initialized to the respective date if parse is successful
	 *  
	 * @throws Exception 	WrongDateException when date is invalid
	 * 						UnrecognizedDateFormat exception when date format cannot be recognized
	 */
	public static Date parse(String string) throws Exception {

		int day = 0, month = 0, year = 0, hrs = 0, mins = 0, temp_day, temp_month, temp_year=1;
		Date temp_date = new Date();
		String[] dateParam;
		foundDayMatch = false;
		foundYearMatch = false;
		foundMonthMatch = false;
		foundMatch = false;
		Boolean foundSplit = true;

		String[] tokens = string.split(" ");

		if (tokens.length == 0) {
			// if user enters an empty string...it is assumed that the event is
			// scheduled for today at 11:59
			return initializeDefaultDate();
		}
		// checks if time is given in the provided string
		for (String s : tokens) {
			Log.add("taskit.logic.DateFormatter.java : initializing time");
			temp_date = initializeTime(s);
			if (foundTime == true) {
				hrs = temp_date.getHour();
				mins = temp_date.getMins();
			}
		}
		//if time isn't given by user, then set time to 11:59
		if (foundTime == false) {
			hrs = 23;
			mins = 59;
		}

		//if only time is given, set date to today's and return
		if (tokens.length == 1 && foundTime == true) {
			Log.add("taskit.logic.DateFormatter.java : initilaizing date to default");
			Date date = initializeDefaultDate();
			return Date.createDate(date.getYear(), date.getMonth(), date.getDay(),
					hrs, mins);
		}

		//try and split by '/' or '\' or '-' to check for format day/month/year
		if (tokens.length >= 1) {
			if (tokens[0].contains(BACK_SLASH)) {
				dateParam = tokens[0].split(BACK_SLASH);
			} else if (tokens[0].contains(HYPHEN)) {
				dateParam = tokens[0].split(HYPHEN);
			} else if (tokens[0].contains(FWD_SLASH)) {
				dateParam = tokens[0].split(FWD_SLASH);
			} else {

				foundSplit = false;
				dateParam = new String[3];
			}
			if (foundSplit == true) {
				//extract day
				Log.add("taskit.logic.DateFormatter.java : finding a day match");
				temp_day = findDayMatch(dateParam[0]);
				if (foundDayMatch == true) {

					day = temp_day;
				}
				if (foundDayMatch == false) {
					//if dayMatch is false try parsing for month
					//to check for format month/date/year
					Log.add("taskit.logic.DateFormatter.java : finding a month match");
					temp_month = findMonthMatch(dateParam[0]);
					if (foundMonthMatch == true) {
						month = temp_month;
						Log.add("taskit.logic.DateFormatter.java : finding a day match");
						temp_day = findDayMatch(dateParam[1]);

						//if month match is found first then try to prase next token for a day match
						if (foundDayMatch == true) {
							day = temp_day;
						}
					}

				} else {
					//check month
					Log.add("taskit.logic.DateFormatter.java : finding a month match");
					temp_month = findMonthMatch(dateParam[1]);
					if (foundMonthMatch == true) {
						month = temp_month;
					}
				}
				//extract year
				if (dateParam.length == 3) {
					Log.add("taskit.logic.DateFormatter.java : initilaizing year");
					temp_year = initializeYear(dateParam[2]);
					if (foundYearMatch == true) {
						year = temp_year;
					}
					else {
						throw new UnrecognizedDateFormatException("third parameter entered for date cannot be recognized");
					}
				} else {
					//if year isn't specified set to today's year
					year = setToCurrentYear();
					foundYearMatch = true;
				}
			}
			if (foundDayMatch == true && foundMonthMatch == true
					&& foundYearMatch == true) {
				foundMatch = true;
				if((tokens.length>2) || (tokens.length==2 && !isTime(tokens[1]))){
					throw new UnrecognizedDateFormatException("Entered Date Format is unrecognized !");
				}
				return  Date.createDate(year,month-1,day,hrs,mins);
			} 
		}

		//if the date has not been found
		if ((foundSplit == false) || (foundMatch == false)) {

			// checks for dates beginning with prefixes i.e. next/this/coming/
			Log.add("taskit.logic.DateFormatter.java : checking for prefix match "); 
			temp_date = findPrefixMatch(tokens, 0);
			if (foundMatch == true){
				day = temp_date.getDay();
				month = temp_date.getMonth();
				year = temp_date.getYear();
				return Date.createDate(year, month-1 , day, hrs, mins);
			}

			// checks for match for today/tomorrow/yesterday etc
			Log.add("taskit.logic.DateFormatter.java : checking for today/tomorrow/yesterday");
			temp_date = findMovingByDay(tokens[0]);
			if (foundMatch == true) {
				if((tokens.length>2) || (tokens.length==2 && !isTime(tokens[1]))){
					throw new UnrecognizedDateFormatException("Entered Date Format is unrecognized !");
				}
				day = temp_date.getDay();
				month = temp_date.getMonth();
				year = temp_date.getYear();
				return Date.createDate(year, month-1, day, hrs, mins);
			}

			// checks for a match for weekday
			//by default for a format like : Friday 2pm or Tuesday, the weekday is assumed to refer to a weekday in the current week
			Log.add("taskit.logic.DateFormatter.java : checking for weekday match");
			temp_date = findWeekdayMatch(THIS, 0, tokens[0]);
			if (foundMatch == true) {
				day = temp_date.getDay();
				month = temp_date.getMonth();
				year = temp_date.getYear();
				return Date.createDate(year, month-1, day, hrs, mins);
			}

		}

		//if a match still hasn't been found, then try parsing for a format such as 20th January 2015 8pm, July 1st 2am
		if (tokens.length>=2) {
			//find match for day
			Log.add("taskit.logic.DateFormatter.java : finding a day match");
			temp_day = findDayMatch(tokens[0]);
			if (foundDayMatch == true) {
				day = temp_day;
			} else {

				//if day is not found try parsing it for month instead for format such as May 2nd 
				Log.add("taskit.logic.DateFormatter.java : finding a month match");
				temp_month = findMonthInWords(tokens[0]);
				if (foundMonthMatch == true) {
					month = temp_month;
				}
			}
			//if first token has been parsed as date then search month
			if (foundDayMatch == true && foundMonthMatch == false) {
				Log.add("taskit.logic.DateFormatter.java : finding a month match");
				temp_month = findMonthInWords(tokens[1]);
				if (foundMonthMatch == true) {
					month = temp_month;
				}
			}
			// if first token has been parsed as month, try finding a match for the day
			if (foundMonthMatch == true && foundDayMatch == false) {
				Log.add("taskit.logic.DateFormatter.java : finding a day match");
				temp_day = findDayMatch(tokens[1]);
				if (foundDayMatch == true) {
					day = temp_day;
				}
			}
			//to initialize date to current for format : july 2nd
			if (foundDayMatch == true && foundMonthMatch == true
					&& tokens.length == 2) {
				GregorianCalendar now = new GregorianCalendar();
				year = now.get(GregorianCalendar.YEAR);
				foundYearMatch = true;
				foundMatch = true;
				return Date.createDate(year, month-1, day, hrs, mins);
			}
			// try to extract year for format : 2nd July 2014 or 3rd Oct 7pm
			if (foundDayMatch == true && foundMonthMatch == true
					&& tokens.length >= 3) {
				//chks if third token is time or could be year
				if (isTime(tokens[2])) {
					foundYearMatch = false;
					if(tokens.length>3){
						throw new UnrecognizedDateFormatException("Entered Date Format is unrecognized !");
					}
				} else {
					Log.add("taskit.logic.DateFormatter.java : finding an year match");
					temp_year = initializeYear(tokens[2]);

				}
				if(!isTime(tokens[2]) && foundYearMatch==false){
					throw new UnrecognizedDateFormatException("Entered Date Format is unrecognized !");
				}
				if (foundYearMatch == false) {
					//if year match not found, initialize to current year  
					GregorianCalendar now = new GregorianCalendar();
					year = now.get(GregorianCalendar.YEAR);
					foundYearMatch = true;
					foundMatch = true;
					Log.add("taskit.logic.DateFormatter.java : create a new date object");
					return Date.createDate(year, month-1, day, hrs, mins);

				} else {
					year = temp_year;
					foundYearMatch = true;
					foundMatch = true;
					if((tokens.length>4) || (tokens.length==4 && !isTime(tokens[3]))){
						throw new UnrecognizedDateFormatException("Entered Date Format is unrecognized !");
					}
					Log.add("taskit.logic.DateFormatter.java : create a new date object");
					return Date.createDate(year, month-1, day, hrs, mins);
				}
			}
		}
		//returns the date extracted from string
		if(foundMatch==false){
			throw new UnrecognizedDateFormatException("Entered Date Format is unrecognized !");
		}
		//when the control reaches here the foundMatch should be false
		assert foundMatch==false;
		Log.add("taskit.logic.DateFormatter.java : create a new date object");
		return Date.createDate(year, month - 1, day, hrs, mins);
	}


	/**
	 * Function extracts two dates from a string
	 * used for schedules
	 *  
	 * @param s
	 * 
	 * @return a list of two dates : start and end
	 * 
	 * @throws Exception	WrongDateException when date is invalid
	 * 						UnrecognizedDateFormat exception when date format cannot be recognized
	 */

	public static List<Date> parseSchedule(String s) throws Exception {
		List<Date> list = new ArrayList<Date>();
		Date dateOne = new Date();
		Date dateTwo = new Date();
		if(!s.contains(TO)) {
			throw new UnrecognizedDateFormatException("Entered Date Format is unrecognized !");
		}
		String[] twoDates = s.split(TO);
		twoDates[0].trim();
		twoDates[1].trim();

		String[] dateTwoParams = twoDates[1].split(" ");
		// if first date has missing time initialize it to 12 am 
		if (isTimeMissing(twoDates[0])) {
			Log.add("taskit.logic.DateFormatter.java : parsing a date");
			dateOne = parse(twoDates[0]);
			Log.add("taskit.logic.DateFormatter.java : create a new date object");
			dateOne = Date.createDate(dateOne.getYear(), dateOne.getMonth(),
					dateOne.getDay(), 0, 0);
		} else {
			Log.add("taskit.logic.DateFormatter.java : parsing a date");
			dateOne = parse(twoDates[0]);
		}

		//if date hasn't been specified for second date eg. : today 3pm to 5pm
		//initialize date with first one's
		if (isTime(twoDates[1]) == true && dateTwoParams.length==1) {
			String stringDateTwo = "" + dateOne.getDay() + FWD_SLASH
					+ (dateOne.getMonth() + 1) + FWD_SLASH + dateOne.getYear() + " "
					+ twoDates[1];
			dateTwo = parse(stringDateTwo);
		} else {
			dateTwo = parse(twoDates[1]);
		}
		list.add(dateOne);
		list.add(dateTwo);
		return list;
	}


}